
#include <dirent.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <netinet/in.h>
#include <sys/param.h>
#include <sys/socket.h>
#include <unistd.h>

int main(int argc, const char* argv[])
{
    uint16_t port = 2533; /* default port number */
    int server_sock;
    struct sockaddr_in server_addr;

    switch(argc)
    {
    case 3:
        port = (uint16_t)strtoul(argv[2], NULL, 0);
        /* no break */

    case 2:
        if(chdir(argv[1]) < 0) /* change working path to requested dir */
        {
            perror(argv[1]);
            exit(EXIT_FAILURE);
        }
        break;

    default:
        fprintf(stderr, "usage: %s dir [port]\n", argv[0]);
        exit(EXIT_FAILURE);
    }

    if((server_sock = socket(AF_INET, SOCK_STREAM, 0)) < 0)
    {
        perror("socket");
        exit(EXIT_FAILURE);
    }

    memset(&server_addr, 0, sizeof(server_addr));
    server_addr.sin_family = AF_INET;
    server_addr.sin_addr.s_addr = INADDR_ANY;
    server_addr.sin_port = htons(port);

    if(bind(server_sock, (struct sockaddr *)&server_addr, sizeof(server_addr)) < 0)
    {
        perror("bind");
        close(server_sock);
        exit(EXIT_FAILURE);
    }

    listen(server_sock, 1);

    while(1)
    {
        struct sockaddr_in client_addr;
        socklen_t client_addr_len = sizeof(client_addr);
        int client_sock;
        FILE* client_sock_file;

        if((client_sock = accept(server_sock, (struct sockaddr *)&client_addr, &client_addr_len)) < 0)
        {
            perror("accept");
            continue;
        }

        client_sock_file = fdopen(client_sock, "a+");

        fwrite("Welcome! I am simple-file-server.\r\n", 1, 35, client_sock_file);

        fseek(client_sock_file, 0, SEEK_CUR); /* Force change of stream direction */

        /* session loop */
        while(1)
        {
            char request[4096] = { 0 };
            char* method;
            char* args;

            /* request */
            if(!fgets(request, sizeof(request), client_sock_file))
            {
                if(ferror(client_sock_file))
                {
                    perror("fgets");
                }

                goto end_session;
            }

            method = strtok(request, " \t\r\n");
            args = strtok(NULL, " \t\r\n");

            if(!method)
            {
                fputs("invalid request: <empty>\n", stderr);
            }
            else if(strcmp(method, "DIR") == 0)
            {
                DIR *dir;

                /* Eat up headers */
                { int _; while(fscanf(client_sock_file, "%n%*1[\r]%*1[\n]%n", &_, &_) >= 0 && !_ && fscanf(client_sock_file, "%*[^\r]") >= 0 && fscanf(client_sock_file, "%*1[\r]%*1[\n]") >= 0); }

                fseek(client_sock_file, 0, SEEK_CUR); /* Force change of stream direction */

                if(args != NULL)
                {
                    fprintf(stderr, "%s: invalid arguments\n", method);
                }
                else if((dir = opendir(".")) != NULL)
                {
                    struct dirent* entry;

                    fputs("List-Files:", client_sock_file);

                    while((entry = readdir(dir)) != NULL)
                    {
                        if(entry->d_type == DT_REG)
                        {
                            fprintf(client_sock_file, " \"%s\"", entry->d_name);
                        }
                    }

                    fputs("\r\n\r\n", client_sock_file);
                }
            }
            else if(strcmp(method, "GET") == 0)
            {
                FILE* file;

                /* Eat up headers */
                { int _; while(fscanf(client_sock_file, "%n%*1[\r]%*1[\n]%n", &_, &_) >= 0 && !_ && fscanf(client_sock_file, "%*[^\r]") >= 0 && fscanf(client_sock_file, "%*1[\r]%*1[\n]") >= 0); }

                fseek(client_sock_file, 0, SEEK_CUR); /* Force change of stream direction */

                if(args == NULL)
                {
                    fprintf(stderr, "%s: missing arguments\n", method);
                }
                else if((file = fopen(args, "rb")) == NULL)
                {
                    perror(args);
                }
                else
                {
                    long content_length;

                    /* reading file size */
                    fseek(file, 0, SEEK_END);
                    content_length = ftell(file);
                    fseek(file, 0, SEEK_SET);

                    /* sending relevant headers */
                    fprintf(client_sock_file, "Content-Length: %ld\r\n\r\n", content_length);

                    /* uploading file content */
                    while(content_length > 0)
                    {
                        char buffer[4096];
                        size_t len;

                        if((len = fread(buffer, 1, MIN(sizeof(buffer), content_length), file)) == 0)
                        {
                            if(ferror(file))
                            {
                                perror("fread");
                            }

                            break;
                        }
                        else if(fwrite(buffer, 1, len, client_sock_file) != len)
                        {
                            perror("fwrite");
                            break;
                        }

                        content_length -= len;
                    }

                    fclose(file);
                }
            }
            else if(strcmp(method, "PUT") == 0)
            {
                FILE* file;

                if(args == NULL)
                {
                    fprintf(stderr, "%s: missing arguments\n", method);
                }
                else if((file = fopen(args, "wb")) == NULL)
                {
                    perror(args);
                }
                else
                {
                    uint32_t content_length = 0;

                    /* parsing relevant headers */
                    while(1)
                    {
                        char header[4096] = { 0 };

                        if(!fgets(header, sizeof(header), client_sock_file))
                        {
                            if(ferror(client_sock_file))
                            {
                                perror("fgets");
                            }

                            goto end_session;
                        }
                        else if(strlen(header) == 0 || (strlen(header) == 1 && header[0] == '\r') || (strlen(header) == 2 && header[0] == '\r' && header[1] == '\n'))
                        {
                            /* empty header indicates end of headers */
                            break;
                        }

                        /* handling Content-Length header */
                        sscanf(header, "Content-Length: %ud", &content_length);
                    }

                    /* downloading file content */
                    while(content_length > 0)
                    {
                        char buffer[4096];
                        size_t len;

                        if((len = fread(buffer, 1, MIN(sizeof(buffer), content_length), client_sock_file)) == 0)
                        {
                            if(ferror(client_sock_file))
                            {
                                perror("fread");
                            }

                            goto end_session;
                        }
                        else if(fwrite(buffer, 1, len, file) != len)
                        {
                            perror("fwrite");
                            goto end_session;
                        }

                        content_length -= len;
                    }

                    fclose(file);
                }
            }
            else
            {
                /* Eat up headers */
                { int _; while(fscanf(client_sock_file, "%n%*1[\r]%*1[\n]%n", &_, &_) >= 0 && !_ && fscanf(client_sock_file, "%*[^\r]") >= 0 && fscanf(client_sock_file, "%*1[\r]%*1[\n]") >= 0); }

                fprintf(stderr, "invalid request: %s\n", method);
            }

            fseek(client_sock_file, 0, SEEK_CUR); /* Force change of stream direction */
        }

end_session:

        fclose(client_sock_file);
        close(client_sock);
    }

    close(server_sock);

    return EXIT_FAILURE;
}
